using System;
using System.Threading;
using System.Collections.Generic;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace Foundry.Engine
{
	
	public enum ThreadVector
	{
		RenderThread,
		PhysicsThread,
		ResourceThread,
		LogicThread,
		All = RenderThread | PhysicsThread | ResourceThread | LogicThread
	}
	
	public class App
	{
		Scene current_scene;
		
		// Callstacks for all threads
		private Queue<Action> RenderCallStack;
		private Queue<Action> ResourceCallStack;
		private Queue<Action> LogicCallStack;
		private Queue<Action> PhysicsCallStack;
		
		// Determines if the application is still running. If not, threads should exit.
		volatile bool app_running;
		volatile ThreadVector threads;
		
		private System.Windows.Forms.Form MainWindow;
		
		volatile bool render_ready;
		volatile bool logic_ready;
		volatile bool physics_ready;
		//volatile bool resource_ready;
		volatile float delta_time;
		
		public float DeltaTime { get { return delta_time; } }
		
		long old_time;
		
		private Camera current_camera;
		
		public Camera CurrentCamera { get { return current_camera ?? DebugCamera;} set { current_camera = value; } }
		
		private static readonly Camera DebugCamera = new Camera();
		
		public Scene CurrentScene { 
			get 
			{ 
				return current_scene; 
			}
			set
			{
				// If any of the root nodes are resilient, move them to the new scene.
				if(current_scene != null)
				{
					foreach(var child in current_scene.Children)
					{
						var nd = child as Node;
						if(nd != null && nd.Resilient)
						{
							nd.Scene = value;
						}
					}
				}
				current_scene = value;
				
			}
		}
		
		private void StartRender()
		{
			GraphicsContext.ShareContexts = true;
			var th = new Thread(new ThreadStart(RenderThreadOperation));
			th.Name = "Render Thread";
			th.Start();
		}
		
		private void StartResource()
		{
			var th = new Thread(new ThreadStart(ResourceThreadOperation));
			th.Name = "Resource thread";
			th.Start();
		}
		
		private void StartLogic()
		{
			var th = new Thread(new ThreadStart(LogicThreadOperation));
			th.Name = "Logic thread";
			th.Start();
		}
		
		private static App current_app;
		
		public static App CurrentApplication { get { return current_app; } }
		
		public void Start()
		{
			current_app = this;
			render_ready = true;
			physics_ready = true;
			logic_ready = true;
			//resource_ready= true;
			
			
			threads = ThreadVector.PhysicsThread;
			app_running = true;
			StartRender();
			StartLogic();
			
			
			
			old_time = System.Diagnostics.Stopwatch.GetTimestamp();
			
			MainLoop();
		}	
			
		private void MainLoop()
		{
			while(app_running)
			{
				var new_time =System.Diagnostics.Stopwatch.GetTimestamp();
				var dif =new_time - old_time;
				delta_time = dif / (float)System.Diagnostics.Stopwatch.Frequency;
				DispatchRenderCall(new Action(Clear));
				ProcessNode(current_scene);
				DispatchRenderCall(new Action(Present));
				
				// Wait for frame crucial threads to complete to keep them from choking
				while(!(render_ready && physics_ready && logic_ready))
					Thread.Sleep(10);
				old_time= new_time;
			}
			
				
		}
	
		public void Stop()
		{
			CurrentScene = null;
			GC.Collect(); // Force collection to pick up all disposed objects
			app_running = false;
		}
		
		protected virtual void PostProcess()
		{
		}
		
		protected virtual void PreProcess()
		{
		}
		
		private void Clear()
		{
			OpenTK.Graphics.OpenGL.GL.ClearColor(Color4.DodgerBlue);
			OpenTK.Graphics.OpenGL.GL.Clear( OpenTK.Graphics.OpenGL.ClearBufferMask.ColorBufferBit | OpenTK.Graphics.OpenGL.ClearBufferMask.DepthBufferBit);
		}
		
		private void Present()
		{
			try
			{
			(MainWindow.Controls[0] as GLControl).SwapBuffers();
			}
			catch(Exception)
			{
			}
		}
			
		
		private void ProcessObject(Foundry.Engine.BaseObject obj)
		{
			var node = obj as Engine.Node;
			var modifier = obj as Engine.Modifier;
			var trans = obj as Transform;
			if(trans !=null)
				trans.abs_cache=trans.GetAbsoluteTransform();
			
			if(obj.StepAction != null && obj.Enabled)
				DispatchLogicCall(obj.StepAction);
			if(obj.initialized && obj.DrawAction != null && obj.Visible)
				DispatchRenderCall(obj.DrawAction);
			if(obj.initialized && obj.DebugDraw != null && obj.Visible)
				DispatchRenderCall(obj.DebugDraw);
			if(node != null && obj.Enabled)
				ProcessNode(node);
			else if(modifier != null && obj.Enabled)
				ProcessModifier(modifier);
				
		}
		
		private void ProcessNode(Engine.Node node)
		{
			
			foreach(var child in node.Modifiers)
				ProcessObject(child);
			
			lock(node.Children)
			{
				foreach(var child in node.Children)
					ProcessObject(child);
			}
		}
		
		private void ProcessModifier(Engine.Modifier mod)
		{
			
		}
		
		/// <summary>
		/// Dispatches a function call to the rendering thread 
		/// </summary>
		/// <param name="act">
		/// A <see cref="Action"/> to be performed.
		/// </param>	
		/// <remarks> Action is called on first oppurtunity, not immediatly.</remarks>
		public void DispatchRenderCall(Action act)
		{
			render_ready = false;
			lock(RenderCallStack)
				RenderCallStack.Enqueue(act);
		}
		
		/// <summary>
		/// Dispatches a function call to the logic thread 
		/// </summary>
		/// <param name="act">
		/// <see cref="Action"/> to perform.
		/// </param>
		public void DispatchLogicCall(Action act)
		{
			logic_ready = false;
			lock(LogicCallStack)
				LogicCallStack.Enqueue(act);
		}
		/// <summary>
		/// Dispatches a function call to the physics thread 
		/// </summary>
		/// <param name="act">
		/// <see cref="Action"/> to perform.
		/// </param>
		public void DispatchPhysicsCall(Action act)
		{
			physics_ready= false;
			lock(PhysicsCallStack)
				PhysicsCallStack.Enqueue(act);
		}
		
		/// <summary>
		/// Dispatches a function call to the resource thread 
		/// </summary>
		/// <param name="act">
		/// <see cref="Action"/> to perform.
		/// </param>
		public void DispatchResourceCallStack(Action act)
		{
			//resource_ready = false;
			lock(RenderCallStack)
				RenderCallStack.Enqueue(act);
		}
		
			
		/// <summary>
		/// Main operational entry point for the resource thread 
		/// </summary>
		private void ResourceThreadOperation()
		{
			// Initialize context. Should share lists with the rendering thread
			OpenTK.GLControl ct = new OpenTK.GLControl(GraphicsMode.Default, 3, 2, GraphicsContextFlags.ForwardCompatible);		

			ct.MakeCurrent();
			
			threads |= ThreadVector.ResourceThread;
			
			ThreadOperation(ResourceCallStack);
			
			threads = threads & ~ThreadVector.ResourceThread;
		}
		
		private void RenderThreadOperation()
		{
			
			
			MainWindow = new System.Windows.Forms.Form();
			MainWindow.Show();
			OpenTK.GLControl ct = new OpenTK.GLControl(GraphicsMode.Default, 3, 2, GraphicsContextFlags.Debug);
			
			ct.Dock = System.Windows.Forms.DockStyle.Fill;
			
			MainWindow.Controls.Add(ct);
			
			//ct.Context.MakeCurrent();
			GraphicsContext.ShareContexts = true;
			StartResource();
			threads |= ThreadVector.RenderThread;
			
			MainWindow.FormClosing += HandleMainWindowFormClosing;
			MainWindow.ResizeEnd += HandleMainWindowResizeEnd;
			
			GL.Enable(EnableCap.CullFace);
			GL.Enable(EnableCap.DepthTest);
			
			while(app_running)
			{
				System.Windows.Forms.Application.DoEvents();
				Thread.Sleep(0);
				lock(RenderCallStack)
				{
					if(RenderCallStack.Count > 0)
					{
						while(RenderCallStack.Count > 0)
							RenderCallStack.Dequeue()();
						render_ready = true;
					}
				}
			}
			
			threads = threads & ~ThreadVector.RenderThread;
			threads = threads & ~ThreadVector.PhysicsThread;
			
		}

		void HandleMainWindowResizeEnd (object sender, EventArgs e)
		{
			OpenTK.Graphics.OpenGL.GL.Viewport(MainWindow.ClientSize);
		}

		void HandleMainWindowFormClosing (object sender, System.Windows.Forms.FormClosingEventArgs e)
		{
			Stop();
			while(threads != ThreadVector.PhysicsThread)
			{
				System.Windows.Forms.Application.DoEvents();
				Thread.Sleep(0);
			}
		}
		
		/// <summary>
		/// Performs default operations for a thread with a callstack 
		/// </summary>
		/// <param name="stack">
		/// <see cref="Queue<Action>"/> queue with actions to perform on the thread
		/// </param>
		private void ThreadOperation(Queue<Action> stack)
		{
			while(app_running)
			{
				Thread.Sleep(10);
				
				lock(stack)
				{
					if(stack.Count > 0)
					{
						while(stack.Count > 0)
							stack.Dequeue()();
					}
				}
			}
		}
		private void PhysicsThreadOperation(Queue<Action> stack)
		{
			while(app_running)
			{
				Thread.Sleep(10);
				
				lock(PhysicsCallStack)
				{
					if(PhysicsCallStack.Count > 0)
					{
						while(PhysicsCallStack.Count > 0)
							PhysicsCallStack.Dequeue()();
						physics_ready = true;
					}
				}
			}
		}		
		private void LogicThreadOperation()
		{
			threads |= ThreadVector.LogicThread;			
			
			while(app_running)
			{
				Thread.Sleep(10);
				
				lock(LogicCallStack)
				{
					if(LogicCallStack.Count > 0)
					{
						while(LogicCallStack.Count > 0)
							LogicCallStack.Dequeue()();
						logic_ready = true;
					}
				}
			}
		}
		
		
		public App()
		{
			LogicCallStack= new Queue<Action>();
			RenderCallStack = new Queue<Action> ();
			ResourceCallStack = new Queue<Action>();
			PhysicsCallStack = new Queue<Action>();
			current_scene = new Scene(this);
			
		}
	}
}

